package com.ym.core.shiro;

import com.ym.core.redis.RedisKeyEnum;
import com.ym.core.shiro.realm.SystemShiroRealm;
import com.ym.core.shiro.realm.UserAccPwdShiroRealm;
import com.ym.core.shiro.realm.UserAccSmsShiroRealm;
import com.ym.core.shiro.realm.WxShiroRealm;
import com.ym.core.utils.EnvUtil;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 *
 */
@Configuration
public class ShiroConfig {

	@Autowired
	private EnvUtil envUtil;

	@Autowired
	private ShiroSessionConfig shiroSessionConfig;

	@Resource
	private RedisProperties redisProperties;

	@Bean
	public ShiroFilterFactoryBean shirFilter(org.apache.shiro.mgt.SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager);

		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
		//开放swagger
		filterChainDefinitionMap.put("/v2/api-docs", "anon");
		filterChainDefinitionMap.put("/v2/api-docs-ext", "anon");
		filterChainDefinitionMap.put("/doc.html", "anon");
		filterChainDefinitionMap.put("/webjars/**", "anon");
		filterChainDefinitionMap.put("/swagger-resources/**", "anon");
		filterChainDefinitionMap.put("/swagger-ui.html", "anon");
		if (envUtil.isApi()) {
			apiUrlMappingSet(filterChainDefinitionMap);
		} else {
			pcUrlMappingSet(filterChainDefinitionMap);
		}
		shiroFilterFactoryBean.setLoginUrl("/unAuth");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}

	/**
	 * api端配置
	 *
	 * @param filterChainDefinitionMap
	 */
	private void pcUrlMappingSet(Map<String, String> filterChainDefinitionMap) {
		// 配置不会被拦截的链接 顺序判断
		filterChainDefinitionMap.put("/open/api/**", "anon");
		// 要认证的接口
		filterChainDefinitionMap.put("/soso/**", "authc");
		filterChainDefinitionMap.put("/admin/**", "authc");
		filterChainDefinitionMap.put("/medical/**", "authc");
	}


	/**
	 * api端配置
	 *
	 * @param filterChainDefinitionMap
	 */
	private void apiUrlMappingSet(Map<String, String> filterChainDefinitionMap) {
		// 开放全部
		filterChainDefinitionMap.put("/**", "anon");

		filterChainDefinitionMap.put("/wx/logout", "logout");
		filterChainDefinitionMap.put("/wx/login", "anon");
	}

	/**
	 * 凭证匹配器
	 * （由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
	 * ）
	 *
	 * @return
	 */
	@Bean
	public HashedCredentialsMatcher hashedCredentialsMatcher() {
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		hashedCredentialsMatcher.setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME);//散列算法:这里使用MD5算法;
		hashedCredentialsMatcher.setStoredCredentialsHexEncoded(false);
		hashedCredentialsMatcher.setHashIterations(1024);
		return hashedCredentialsMatcher;
	}

	@Bean
	public SystemShiroRealm myShiroRealm() {
		SystemShiroRealm myShiroRealm = new SystemShiroRealm();
		myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
		return myShiroRealm;
	}

	@Bean
	public WxShiroRealm wxShiroRealm() {
		WxShiroRealm myShiroRealm = new WxShiroRealm();
		return myShiroRealm;
	}

	@Bean
	public UserAccSmsShiroRealm userAccSmsShiroRealm() {
		UserAccSmsShiroRealm userAccSmsShiroRealm = new UserAccSmsShiroRealm();
		return userAccSmsShiroRealm;
	}


	@Bean
	public UserAccPwdShiroRealm userAccPwdShiroRealm() {
		UserAccPwdShiroRealm userAccPwdShiroRealm = new UserAccPwdShiroRealm();
		userAccPwdShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
		return userAccPwdShiroRealm;
	}

	@Bean
	public org.apache.shiro.mgt.SecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		MyModularRealmAuthenticator modularRealmAuthenticator = new MyModularRealmAuthenticator();
		modularRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
		securityManager.setAuthenticator(modularRealmAuthenticator);
		Set<Realm> realmHashSet = new HashSet<Realm>();
		realmHashSet.add(myShiroRealm());
		realmHashSet.add(wxShiroRealm());
		realmHashSet.add(userAccPwdShiroRealm());
		realmHashSet.add(userAccSmsShiroRealm());
		securityManager.setRealms(realmHashSet);
		// 自定义session管理 使用redis
		securityManager.setSessionManager(sessionManager());
		// 自定义缓存实现 使用redis
		securityManager.setCacheManager(myCacheManager());
		return securityManager;
	}

	//自定义sessionManager
	@Bean
	public SessionManager sessionManager() {
		MySessionManager mySessionManager = new MySessionManager();
		mySessionManager.setSessionDAO(redisSessionDAO());
		mySessionManager.setSessionIdCookie(simpleCookie());
		//设置session过期时间为1小时(单位：毫秒)，默认为30分钟
		mySessionManager.setGlobalSessionTimeout(TimeUnit.MINUTES.toMillis(shiroSessionConfig.getExpireTime()));
		// 多久检测一次过期、无效的session.默认60分钟
		mySessionManager.setDeleteInvalidSessions(true);
		mySessionManager.setSessionValidationSchedulerEnabled(true);
		mySessionManager.setSessionValidationInterval(TimeUnit.MINUTES.toMillis(shiroSessionConfig.getDbSyncPeriod()));
		return mySessionManager;
	}

	@Bean
	public SimpleCookie simpleCookie() {
		SimpleCookie simpleCookie = new SimpleCookie("shiro.sesssion.health");
		simpleCookie.setPath("/");
		simpleCookie.setHttpOnly(true);
		simpleCookie.setSecure(true);
		//记住我有效期长达30天
		//simpleCookie.setMaxAge((int)redisExpire.toMillis());
		return simpleCookie;
	}

	/**
	 * 配置shiro redisManager
	 * <p>
	 * 使用的是shiro-redis开源插件
	 *
	 * @return
	 */
	public RedisManager redisManager() {
		RedisManager redisManager = new RedisManager();
		redisManager.setHost(redisProperties.getHost());
		redisManager.setPort(redisProperties.getPort());
		redisManager.setDatabase(redisProperties.getDatabase());
		redisManager.setTimeout((int) redisProperties.getTimeout().toMillis());
		// redisManager.setPassword(redisProperties.getPassword());
		return redisManager;
	}

	/**
	 * cacheManager 缓存 redis实现
	 * <p>
	 * 使用的是shiro-redis开源插件
	 *
	 * @return
	 */
	@Bean
	public RedisCacheManager myCacheManager() {
		RedisCacheManager redisCacheManager = new RedisCacheManager();
		redisCacheManager.setRedisManager(redisManager());
		return redisCacheManager;
	}

	/**
	 * RedisSessionDAO shiro sessionDao层的实现 通过redis
	 * <p>
	 * 使用的是shiro-redis开源插件
	 */
	@Bean
	public RedisSessionDAO redisSessionDAO() {
		RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
		redisSessionDAO.setRedisManager(redisManager());
		redisSessionDAO.setKeyPrefix(RedisKeyEnum.SHIRO_SESSION.build(":"));
		return redisSessionDAO;
	}

	/**
	 * 开启shiro aop注解支持.
	 * 使用代理方式;所以需要开启代码支持;
	 *
	 * @param mySecurityManager
	 * @return
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager mySecurityManager) {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(mySecurityManager);
		return authorizationAttributeSourceAdvisor;
	}

	@Bean
	public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
		advisorAutoProxyCreator.setProxyTargetClass(true);
		return advisorAutoProxyCreator;
	}
}
